// 14 重载运算与类型转换
/**
 * 当运算符被用于类类型的对象时，C++语言允许我们为其指定新的含义；同时，我们也能自定义类类型之间的转换规则。和内置类型的转换一样，类类型转换隐式地将一种类型的对象转换成另一种我们所需类型的对象。
 * 当运算符作用于类类型的运算对象时，可以通过运算符重载重新定义该运算符的含义。明智地使用运算符重载能令我们的程序更易于编写和阅读。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include "../Chapter13/13.5.cc" // 不能编译，因为重复定义的main函数
#include "../Chapter12/12.1.6.cc" // 不能编译，因为重复定义的main函数
#include <functional>

// 最好不要在两个类之间构建相同的类型转换
struct B;
struct A
{
    A() = default; // 默认构造函数
    A(const B&);   // 把一个B转换成A（隐式转换机制）
    // 其他数据成员
};
struct B
{
    operator A() const; // 也是把一个B转换成A（类型转换运算符）
    // 其他数据成员
};
A f(const A&);
B b;
A a = f(b); // 二义性错误，含义是f(B::operatorA()) 还是f(A::A(const B&)) ？

int main()
{
    // 14.9 重载、类型转换与运算符
    // 在7.5.4节（第263页）中我们看到由一个实参调用的非显式构造函数定义了一种隐式的类型转换，这种构造函数将实参类型的对象转换成类类型。
    // 我们同样能定义对于类类型的类型转换，通过定义类型转换运算符可以做到这一点。
    // 转换构造函数和类型转换运算符共同定义了类类型转换（class-type conversions），这样的转换有时也被称作用户定义的类型转换（user-defined conversions）。

    // 14.9.2 避免有二义性的类型转换
    // 如果类中包含一个或多个类型转换，则必须确保在类类型和目标类型之间只存在唯一一种转换方式。否则的话，我们编写的代码将很可能会具有二义性。
    // 第一种情况是：两个类提供相同的类型转换：例如，当A类定义了一个接受B类对象的转换构造函数，同时B类定义了一个转换目标是A类的类型转换运算符时，我们就说它们提供了相同的类型转换。
    // 第二种情况是：类定义了多个转换规则，而这些转换涉及的类型本身可以通过其他类型转换联系在一起。最典型的例子是算术运算符，对某个给定的类来说，最好只定义最多一个与算术类型有关的转换规则。
    // 通常情况下，不要为类定义相同的类型转换，也不要在类中定义两个及两个以上转换源或转换目标是算术类型的转换。

    // 实参匹配和相同的类型转换
    // 因为同时存在两种由B获得A的方法，所以造成编译器无法判断应该运行哪个类型转换，也就是说，对f的调用存在二义性。
    // 该调用可以使用以B为参数的A的构造函数，也可以使用B当中把B转换成A的类型转换运算符。因为这两个函数效果相当、难分伯仲，所以该调用将产生错误。
    // 如果我们确实想执行上述的调用，就不得不显式地调用类型转换运算符或者转换构造函数：
    A a1 = f(b.operator A()); // 正确，使用B的类型转换运算符
    A a2 = f(A(b));           // 正确，使用A的构造函数
    // 值得注意的是，我们无法使用强制类型转换来解决二义性问题，因为强制类型转换本身也面临二义性。

    // 二义性与转换目标为内置类型的多重类型转换
    // 另外如果类定义了一组类型转换，它们的转换源（或者转换目标）类型本身可以通过其他类型转换联系在一起，则同样会产生二义性的问题。
    // 最简单也是最困扰我们的例子就是类当中定义了多个参数都是算术类型的构造函数，或者转换目标都是算术类型的类型转换运算符。
    // 当我们使用两个用户定义的类型转换时，如果转换函数之前或之后存在标准类型转换，则标准类型转换将决定最佳匹配到底是哪个。

    // 提示：类型转换与运算符
    // 要想正确地设计类的重载运算符、转换构造函数及类型转换函数，必须加倍小心。尤其是当类同时定义了类型转换运算符及重载运算符时特别容易产生二义性。以下的经验规则可能对你有所帮助：
    // · 不要令两个类执行相同的类型转换：如果Foo类有一个接受Bar类对象的构造函数，则不要在Bar类中再定义转换目标是Foo类的类型转换运算符。
    // · 避免转换目标是内置算术类型的类型转换。特别是当你已经定义了一个转换成算术类型的类型转换时，接下来
    //   — 不要再定义接受算术类型的重载运算符。如果用户需要使用这样的运算符，则类型转换操作将转换你的类型的对象，然后使用内置的运算符。
    //   — 不要定义转换到多种算术类型的类型转换。让标准类型转换完成向其他算术类型转换的工作。
    //     一言以蔽之：除了显式地向bool类型的转换之外，我们应该尽量避免定义类型转换函数并尽可能地限制那些“显然正确”的非显式构造函数。

    // 重载函数与转换构造函数
    // 如果在调用重载函数时我们需要使用构造函数或者强制类型转换来改变实参的类型，则******这通常意味着程序的设计存在不足******。
    // 在调用重载函数时，如果需要额外的标准类型转换，则该转换的级别只有当所有可行函数都请求同一个用户定义的类型转换时才有用。
    // 如果所需的用户定义的类型转换不止一个，则该调用具有二义性。

    // 重载函数与用户定义的类型转换
    // 因为调用重载函数所请求的用户定义的类型转换不止一个且彼此不同，所以该调用具有二义性。即使其中一个调用需要额外的标准类型转换而另一个调用能精确匹配，编译器也会将该调用标示为错误。


    return 0;
}